home *** CD-ROM | disk | FTP | other *** search
/ MacWorld 1999 September (IDG) / Sep99.iso / Shareware World / Utilities / Text Processing / Alpha / Tcl / Modes / diffMode.tcl < prev    next >
Encoding:
Text File  |  1999-04-14  |  30.8 KB  |  1,079 lines  |  [TEXT/ALFA]

  1. ## -*-Tcl-*-
  2.  # ###################################################################
  3.  #  Vince's Additions - an extension package for Alpha
  4.  # 
  5.  #  FILE: "diffMode.tcl"
  6.  #                                    created: 7/3/95 {11:15:02 pm} 
  7.  #                                last update: 14/4/1999 {5:49:33 pm} 
  8.  #  Author: Vince Darley
  9.  #  E-mail: <darley@fas.harvard.edu>
  10.  #    mail: Division of Engineering and Applied Sciences, Harvard University
  11.  #          Oxford Street, Cambridge MA 02138, USA
  12.  #     www: <http://www.fas.harvard.edu/~darley/>
  13.  #  
  14.  # improvements Copyright (c) 1997-1998  Vince Darley, all rights reserved
  15.  # 
  16.  #  Description: 
  17.  #  
  18.  #  Largely re-written Diff mode for Alpha.  Still under construction,
  19.  #  but already a lot better than the old one.  Basic features:
  20.  #  
  21.  #  A 'Diff' menu, which contains commonly used options.
  22.  #  
  23.  #  Uses Alpha's 'marks' so that you can patch diffs back and forth
  24.  #  between files without losing the correct location in the file.
  25.  #  (previously if you modified one of the original windows, all line
  26.  #  numbers after that would be incorrect)
  27.  #  
  28.  #  Limitations:
  29.  #  
  30.  #  Sadly a lot of Alpha's window manipulation commands only work
  31.  #  on the foremost window.  This means this code is slowed down a
  32.  #  lot because it often has to bring a window to the front before
  33.  #  reading/writing into it.  There is a flag to setup a hack which
  34.  #  helps with this, at the expense of colours in the windows.
  35.  # 
  36.  #  History:
  37.  # 
  38.  #  modified by  rev reason
  39.  #  -------- --- --- -----------
  40.  #  7/3/95   Pete? 1.0 original
  41.  #  3/9/97   VMD 2.0 much improved version
  42.  #  03/23/98 VMD and Jon Guyer 2.0-3.0 various fixes and Voodoo 
  43.  # ###################################################################
  44.  ##
  45.  
  46. # Usage: diff [-#] [-abBcdefhHilnNprstTuvw] [-C lines] [-F regexp] [-I regexp]
  47. #        [-L label [-L label]] [-S file] [-D symbol] [+ignore-blank-lines]
  48. #        [+context[=lines]] [+unified[=lines]] [+ifdef=symbol]
  49. #        [+show-function-line=regexp]
  50. #        [+speed-large-files] [+ignore-matching-lines=regexp] [+new-file]
  51. #        [+initial-tab] [+starting-file=file] [+text] [+all-text] [+ascii]
  52. #        [+minimal] [+ignore-space-change] [+ed] [+reversed-ed] [+ignore-case]
  53. #        [+print] [+rcs] [+show-c-function] [+binary] [+brief] [+recursive]
  54. #        [+report-identical-files] [+expand-tabs] [+ignore-all-space]
  55. #        [+file-label=label [+file-label=label]] [+version] path1 path2
  56.  
  57. alpha::mode Diff 3.0 diffMenu {*.diff *.patch} {diffMenu} {
  58.     alpha::package require -loose AlphaTcl 7.1.6
  59.     addMenu diffMenu •288
  60.     namespace eval compare {}
  61.     menu::insert Utils submenu 0 compare
  62.     menu::insert compare items end "windows" "files…" "directories…"
  63.     hook::register requireOpenWindowsHook [list compare windows] 2
  64.     ensureset DiffSig DIFF
  65.     set Diff::handlers(Diff-mode) Diff::runInsideAlpha
  66.     ensureset Diff::handler Diff-mode
  67.     lunion varPrefs(Files) Diff::handler
  68.     # By default Alpha handles the results of diff internally using
  69.     # its Diff mode.  However add-on packages to Alpha can provide
  70.     # alternative Diff handlers.
  71.     newPref var Diff::handler "Diff-mode" global "" Diff::handlers array
  72. } uninstall {
  73.     file delete "$pkg_file"
  74.     file delete [file join ${HOME} Tools "GNU Diff"]
  75. } maintainer {
  76.     "Vince Darley" darley@fas.harvard.edu <http://www.fas.harvard.edu/~darley/>
  77. } help {file "Diff Help"}
  78.  
  79. array set DiffAppSignatures {
  80.     GnuDiff DIFF
  81. }
  82. array set DiffAppScripts {
  83.     GnuDiff {
  84.     {dosc -c $quotedSig -s $flags}
  85.     }
  86. }
  87.  
  88. proc diffMenu {} {}
  89.  
  90. # Generally best to use this setting, but some actions can be a bit
  91. # slower with it on.  Allows you to patch changes back and forth
  92. # between windows automatically, which is otherwise not possible
  93. newPref f useSophisticatedDiffMarking 1 Diff
  94. # A good idea, but can mess up window colours sometimes
  95. # (it's a bit of a hack)
  96. newPref f useFastWindowSwapping 1 Diff
  97. # Slows things down in that it has to scan through Alpha's list of marks
  98. # to find the correct positions for each window, but speeds things up
  99. # because it doesn't need to activate each window in turn.  Try it and see
  100. # for yourself.
  101. newPref f useMarksDontBringToFront 1 Diff
  102. # Up/Down arrows both scroll the diff window and synchronise the viewed
  103. # portion of text in the document windows
  104. newPref f synchroniseMoveAndView 1 Diff Diff::bindUpDown
  105. # You'll probably want this; may slow things down a bit though
  106. newPref f workaroundAlphaColourBug 1 Diff
  107. # Default lines of context to generate when asking Diff to do its magic
  108. newPref var linesOfContext 3 Diff
  109. # Other diff flags you want to send (ignore whitespace etc)
  110. newPref var diffFlags { } Diff
  111. # If you've imported a diff file from a Unix system, this option allows
  112. # you to use it with Alpha too.
  113. newPref f convertSlashToColonInPaths 1 Diff
  114. # If you've imported a diff file from a different directory structure,
  115. # you may need to remove a given prefix so Alpha can find your files
  116. # correctly.
  117. newPref v removeFilePrefix "" Diff
  118.  
  119. Menu -n $diffMenu -p Diff::menuProc -M Diff {
  120.     "rerunDiff"
  121.     "(-"
  122.     "/<I<BpatchIntoLeftWindow"
  123.     "/<I<BpatchIntoRightWindow"
  124.     "(-"
  125.     "cleanUpAndCloseWindows"
  126.     "(-"
  127.     "locateLeftWindow"
  128.     "locateRightWindow"
  129.     "locateLeftDir"
  130.     "locateRightDir"
  131.     "parseDiffWin"
  132. }
  133. Bind 0x7b <z> Diff::patchIntoLeftWindow Diff
  134. Bind 0x7c <z> Diff::patchIntoRightWindow Diff
  135.  
  136. if {[info tclversion] < 8.0} {
  137.     # Bind manually due to bug
  138.     Bind 0x7b <oz> Diff::patchIntoLeftWindow Diff
  139.     Bind 0x7c <oz> Diff::patchIntoRightWindow Diff
  140. }
  141. # do the rest
  142. Bind '\r'        Diff::Select    Diff
  143. Bind '\t'        Diff::View    Diff
  144. Bind Kpad. <c>        Diff::Win
  145. Bind Enter        {Diff::Down;Diff::Select}    Diff
  146. Bind Kpad0        {Diff::Up;Diff::Select}    Diff
  147.  
  148. hook::register closeHook Diff::closing Diff
  149. hook::register openHook Diff::opening Diff
  150.  
  151. proc Diff::bindUpDown {} {
  152.     global DiffmodeVars
  153.     if {$DiffmodeVars(synchroniseMoveAndView)} {
  154.     catch {unBind down         Diff::Down Diff}
  155.     catch {unBind up         Diff::Up Diff}
  156.     Bind down        {Diff::Down;Diff::View}    Diff
  157.     Bind up        {Diff::Up;Diff::View}    Diff
  158.     } else {
  159.     catch {unBind down        {Diff::Down;Diff::View}    Diff}
  160.     catch {unBind up        {Diff::Up;Diff::View}    Diff}
  161.     Bind down         Diff::Down Diff
  162.     Bind up         Diff::Up Diff
  163.     }
  164. }
  165.  
  166. Diff::bindUpDown
  167.  
  168. proc Diff::menuProc {menu item} {
  169.     Diff::$item
  170. }
  171.  
  172. proc Diff::locateLeftWindow {} {
  173.     global Diff::1
  174.     set Diff::1 [getfile "Select your left (old) file:"]
  175.     Diff::Display Diff::1 1 0 1
  176.     Diff::setMarksUp
  177.     if {[info exists Diff::1]} {Diff::mark ${Diff::1} 1 ""}
  178.     Diff::diffWinFront
  179. }
  180.  
  181. proc Diff::locateRightWindow {} {
  182.     global Diff::2
  183.     set Diff::2 [getfile "Select your right (new) file:"]
  184.     Diff::Display Diff::2 0 0 1
  185.     Diff::setMarksUp
  186.     if {[info exists Diff::2]} {Diff::mark ${Diff::2} 0 ""}
  187.     Diff::diffWinFront
  188. }
  189.  
  190. proc Diff::locateLeftDir {} {
  191.     global Diff::leftDir
  192.     set Diff::leftDir [get_directory "Select your left (old) directory:"]
  193.     append Diff::leftDir :
  194. }
  195. proc Diff::locateRightDir {} {
  196.     global Diff::rightDir
  197.     set Diff::rightDir [get_directory "Select your right (new) directory:"]
  198.     append Diff::rightDir :
  199. }
  200.  
  201. proc Diff::rerunDiff {} {
  202.     global diffDir Diff::1 Diff::2
  203.     Diff::diffWinFront
  204.     catch {set d1 ${Diff::1}}
  205.     catch {set d2 ${Diff::2}}
  206.     killWindow
  207.     catch {set Diff::1 $d1}
  208.     catch {set Diff::2 $d2}
  209.     if {$diffDir} {
  210.     Diff::execute 1 {* Directory Comparison *}
  211.     } else {
  212.     Diff::files
  213.     }
  214. }
  215.  
  216. proc Diff::cleanUpAndCloseWindows {} {
  217.     global Diff::1 Diff::2 diffDir
  218.     if {![catch {bringToFront ${Diff::1}}]} {
  219.     removeAllMarks diff-*
  220.     shrinkFull
  221.     killWindow
  222.     }
  223.     
  224.     if {![catch {bringToFront ${Diff::2}}]} {
  225.     removeAllMarks diff-*
  226.     shrinkFull
  227.     killWindow
  228.     }
  229.     Diff::diffWinFront
  230.     killWindow
  231. }
  232.  
  233. proc Diff::closing {{name ""}} {
  234.     global Diff::array Diff::Marked Diff::1 Diff::2
  235.     foreach var [info globals Diff::array*] {
  236.     global $var
  237.     if {[array exists $var]} { unset $var }
  238.     }
  239.     catch {unset Diff::Marked}
  240.     catch {unset Diff::1}
  241.     catch {unset Diff::2}
  242. }
  243.  
  244. ## 
  245.  # -------------------------------------------------------------------------
  246.  # 
  247.  # "Diff::opening" --
  248.  # 
  249.  #  This procedure is called whenever we open a diff window, whether 
  250.  #  a '.diff' file, or whether a window produced by this mode using
  251.  #  'Diff::execute'.  We parse its contents.
  252.  # -------------------------------------------------------------------------
  253.  ##
  254. proc Diff::opening {name} {
  255.     global Diff::window DiffmodeVars Diff::leftDir Diff::rightDir
  256.     set Diff::window $name
  257.     set Diff::leftDir ""
  258.     set Diff::rightDir ""
  259.     
  260.     if {$DiffmodeVars(useSophisticatedDiffMarking)} {
  261.     Diff::parseDiffWin
  262.     }
  263. }
  264.  
  265. # ◊◊◊◊ Parsing diff information ◊◊◊◊ #
  266.  
  267. proc Diff::parseDiffWin {} {
  268.     Diff::diffWinFront
  269.     global diffDir Diff::window
  270.     
  271.     set pos [minPos]
  272.     while 1 {
  273.     set res [search -s -n -f 1 -r 1 "^(diff.*|\[^- \n\r\]+)(\r|\n|\$)" $pos]
  274.     if {$res != ""} {
  275.         set pos [pos::math [lindex $res 0] + 1]
  276.         # if we picked up a 'diff...' line in a context diff
  277.         if {[lookAt $pos] == "i" && [lookAt [nextLineStart [lindex $res 0]]] == "*"} {
  278.         continue
  279.         }
  280.         set t [getText [lindex $res 0] [pos::math [lindex $res 1] - 1]]
  281.         if {[regexp {^\*+$} $t]} {
  282.         set diffDir 1
  283.         # check if the file has changed
  284.         if {[string index [set tt [getText [prevLineStart $pos] $pos]] 0] != " " \
  285.           && [lookAt [pos::math $pos - 3]] != "-" } {
  286.             set to [lindex $tt 1]
  287.             regexp " (.*)\t" $tt "" to
  288.             set p [prevLineStart $pos]
  289.             regexp " (.*)\t" [getText [prevLineStart $p] $p] from
  290.             regsub -all "/" $from ":" from
  291.             regsub -all "/" $to ":" to
  292.             lappend got [list "diff" $from $to]
  293.         }
  294.         set from [lindex [eval getText [search -s -n -f 1 -r 1 {^\*\*\* [0-9]+,[0-9]+} $pos]] 1]
  295.         set to [lindex [eval getText [search -s -n -f 1 -r 1 {^--- [0-9]+,[0-9]+} $pos]] 1]
  296.         lappend got "$from $to"
  297.         } else {
  298.         lappend got $t
  299.         }
  300.     } else {
  301.         break
  302.     }
  303.     }
  304.     set Diff::window [win::Current]
  305.     # now stored all diff items in the list 'got'
  306.     if {[info exists got]} {Diff::storeMarks $got}
  307.     Diff::diffWinFront
  308.     global tileTop tileWidth tileHeight tileLeft
  309.     set top [expr {$tileTop + $tileHeight - 178}]
  310.     sizeWin ${Diff::window} [expr {$tileWidth - 6}] 178
  311.     moveWin ${Diff::window} $tileLeft $top
  312.     
  313. }
  314.  
  315. proc Diff::storeMarks {diffs} {
  316.     global Diff::1 Diff::2 Diff::array
  317.     set suff ""
  318.     foreach m $diffs {
  319.     if {[regexp {^diff} $m]} {
  320.         set suff "/[file tail [lindex $m end]]"
  321.         global Diff::array${suff}
  322.         continue
  323.     }
  324.     set Diff::array${suff}($m) ""
  325.     }
  326. }
  327.  
  328. proc Diff::setMarksUp {{suff ""}} {
  329.     global Diff::array${suff}
  330.     foreach m [array names Diff::array$suff] {
  331.     set scanned [Diff::parseDiffString $m]
  332.     if {[scan $scanned "%s %d %d %d %d" \
  333.       char start1 end1 start2 end2] != 5} { error "Bad diff list!" }
  334.     if {$scanned != ""} {
  335.         set Diff::array${suff}($m) $scanned
  336.     }
  337.     }
  338. }
  339.  
  340. proc Diff::mark {win left {suff ""}} {
  341.     global Diff::array$suff DiffmodeVars
  342.     if {$win != ""} {
  343.     # Alpha somehow remembers the last mode in which it adjusts
  344.     # the window and so forgets all the colours if we cheat the
  345.     # mode switch.
  346.     if {$DiffmodeVars(workaroundAlphaColourBug)} {
  347.         bringToFront $win
  348.     } else {
  349.         Diff::BringToFront $win
  350.     }
  351.     # not strictly necessary, but cleaner
  352.     removeAllMarks diff-*
  353.     if {$left} {
  354.         foreach m [array names Diff::array$suff] {
  355.         scan [set Diff::array${suff}($m)] "%s %d %d" char start1 end1
  356.         setNamedMark "diff-$m" $start1 $start1 $end1
  357.         }
  358.     } else {
  359.         foreach m [array names Diff::array$suff] {
  360.         scan [set Diff::array${suff}($m)] "%s %d %d %d %d" char start1 end1 start2 end2
  361.         setNamedMark "diff-$m" $start2 $start2 $end2
  362.         }
  363.     }
  364.     }
  365. }
  366.  
  367. proc Diff::markUpWindow {diffs} {
  368.     alertnote "Currently a little obsolete; shouldn't be called!"
  369.     if {[info exists Diff::1]} {
  370.     Diff::BringToFront ${Diff::1}
  371.     # not strictly necessry, but cleaner
  372.     removeAllMarks diff-*
  373.     foreach m $diffs {
  374.         scan [set Diff::array($m)] "%s %d %d" char start1 end1
  375.         setNamedMark "diff-$m" $start1 $start1 $end1
  376.     }
  377.     }
  378.     if {[info exists Diff::2]} {
  379.     Diff::BringToFront ${Diff::2}
  380.     # not strictly necessry, but cleaner
  381.     removeAllMarks diff-*
  382.     foreach m $diffs {
  383.         scan [set Diff::array($m)] "%s %d %d %d %d" char start1 end1 start2 end2
  384.         setNamedMark "diff-$m" $start2 $start2 $end2
  385.     }
  386.     }
  387.     
  388. }
  389.  
  390. proc Diff::parseDiffString {text} {
  391.     global Diff::1 Diff::2
  392.     if {![regexp {[acd]} $text char]} {
  393.     # context sensitive
  394.     set char "c"
  395.     if {[scan $text "%d,%d %d,%d" one oned two twod] != 4} {
  396.         return
  397.     }
  398.     } else {
  399.     set res [split $text $char]
  400.     if {![scan [lindex $res 0] "%d,%d" one oned]} return
  401.     if {![scan [lindex $res 1] "%d,%d" two twod]} return
  402.     if {![info exists oned]} { set oned $one }
  403.     if {![info exists twod]} { set twod $two }
  404.     }
  405.     
  406.     if {[info exists Diff::1]} {
  407.     set res [list $char [rowColToPos -w ${Diff::1} $one 0]]
  408.     if {$char != "a"} {
  409.         lappend res [rowColToPos -w ${Diff::1} [expr {$oned + 1}] 0]
  410.     } else {
  411.         lappend res [rowColToPos -w ${Diff::1} $oned 1]
  412.     }
  413.     } else {
  414.     set res [list $char -1 -1]
  415.     }
  416.     
  417.     if {[info exists Diff::2]} {
  418.     lappend res [rowColToPos -w ${Diff::2} $two 0]
  419.     if {$char != "d"} {
  420.         lappend res [rowColToPos -w ${Diff::2} [expr {$twod + 1}] 0]]
  421.     } else {
  422.         lappend res [rowColToPos -w ${Diff::2} $twod 1]
  423.     }
  424.     } else {
  425.     lappend res -1 -1
  426.     }
  427.     return $res
  428. }
  429.  
  430. proc Diff::parseDiffLine {text {is_pos 0}} {
  431.     if {$is_pos} {
  432.     set text [Diff::line $text]
  433.     }
  434.     return [Diff::parseDiffString $text]
  435. }
  436.  
  437. proc Diff::line {pos {f ""}} {
  438.     global diffDir Diff::window DiffmodeVars
  439.     if {$diffDir} {
  440.     if {$f != ""} {upvar $f files}
  441.     if {[lookAt $pos] == "*" || [catch {search -s -f 0 -r 1 "^diff.*(\r|\n|\$)" $pos} res]} {
  442.         set p $pos
  443.         while 1 {
  444.         set res [search -s -f 0 -r 1 "^\\*+(\r|\n|\$)" $p]
  445.         set p [pos::math [lindex $res 0] - 2]
  446.         if {[lookAt [lineStart $p]] != " " && [lookAt $p] != "-"} break
  447.         }
  448.         regexp " (.*)\t" [getText [lineStart $p] $p] "" to
  449.         regexp " (.*)\t" [getText [prevLineStart $p] [lineStart $p]] "" from
  450.         if {[set pr $DiffmodeVars(removeFilePrefix)] != ""} {
  451.         regsub -all "/\./" $to "/" to
  452.         if {[string first $pr $to] == 0} {
  453.             set to [string range $to [string length $pr] end]
  454.         }
  455.         regsub -all "/\./" $from "/" from
  456.         if {[string first $pr $from] == 0} {
  457.             set from [string range $from [string length $pr] end]
  458.         }
  459.         }
  460.         set files [list $from $to]
  461.         set tfrom [lindex [eval getText [search -s -n -f 1 -r 1 {^\*\*\* [0-9]+,[0-9]+} [getPos]]] 1]
  462.         set tto [lindex [eval getText [search -s -n -f 1 -r 1 {^--- [0-9]+,[0-9]+} [getPos]]] 1]
  463.         set text "$tfrom $tto"        
  464.     } else {
  465.         set llen [llength [set files [eval getText $res]]]
  466.         set files [lrange $files [expr {$llen -2}] end]
  467.         set text [getText [lineStart $pos] [pos::math [nextLineStart $pos] - 1]]
  468.     }
  469.     if {$DiffmodeVars(convertSlashToColonInPaths)} {
  470.         regsub -all "/" $files ":" files
  471.     }
  472.     set f [lindex $files end]
  473.     set suff "/[file tail $f]"
  474.     } else {
  475.     set suff ""
  476.     set text [getText [lineStart $pos] [pos::math [nextLineStart $pos] - 1]]
  477.     }
  478.     return "${text}${suff}"
  479. }
  480.  
  481. # ◊◊◊◊ Patching routines ◊◊◊◊ #
  482. proc Diff::patch {w1 w2 left} {
  483.     global DiffmodeVars
  484.     if {$DiffmodeVars(useSophisticatedDiffMarking)} {
  485.     Diff::patchSophisticated $w1 $w2 $left
  486.     } else {
  487.     Diff::patchOld $w1 $w2 $left
  488.     }
  489. }
  490. proc Diff::patchSophisticated {ww1 ww2 left} {
  491.     upvar \#0 $ww1 w1
  492.     upvar \#0 $ww2 w2
  493.     set code [Diff::line [getPos]]
  494.     regexp {([^/]+)(.*)} $code "" mark suff
  495.     global Diff::array${suff}
  496.     if {![info exists w1]} { dialog::errorAlert "No such window" }
  497.     switch "[lindex [set Diff::array${suff}($mark)] 0]${left}" {
  498.     "c1" -
  499.     "c0" {
  500.         if {[info exists w2]} { 
  501.         Diff::BringToFront ${w2}
  502.         gotoMark "diff-$mark"
  503.         set text [getSelect]
  504.         } else {
  505.         # we assume the line is selected in the diff-win
  506.         if {$left} {
  507.             set p [selEnd]
  508.             set e [search -s -f 1 -r 1 {^---.*$} $p]
  509.             set p [lindex $e 1]
  510.             set e [search -s -f 1 -r 1 {^[^>]} $p]
  511.             set text [getText $p [lindex $e 0]]
  512.             regsub -all "\[\n\r\]> " $text "\r" text
  513.             set text [string range $text 1 end]
  514.         } else {
  515.             set p [selEnd]
  516.             set e [search -s -f 1 -r 1 {^---} $p]
  517.             set text [getText $p [lindex $e 0]]
  518.             regsub -all "\[\r\n\]< " $text "\r" text
  519.             set text [string range $text 1 end]
  520.         }
  521.         }
  522.         Diff::BringToFront ${w1}
  523.         gotoMark "diff-$mark"
  524.         replaceText [getPos] [selEnd] $text
  525.     }
  526.     "d1" -
  527.     "a0" {
  528.         Diff::BringToFront ${w1}
  529.         gotoMark "diff-$mark"
  530.         deleteText [getPos] [selEnd]
  531.     }
  532.     "a1" -
  533.     "d0" {
  534.         if {[info exists w2]} { 
  535.         Diff::BringToFront ${w2}
  536.         gotoMark "diff-$mark"
  537.         set text [getSelect]
  538.         } else {
  539.         # we assume the line is selected in the diff-win
  540.         if {$left} {
  541.             set p [selEnd]
  542.             set e [search -s -f 1 -r 1 {^---.*$} $p]
  543.             set p [lindex $e 1]
  544.             set e [search -s -f 1 -r 1 {^[^>]} $p]
  545.             set text [getText $p [lindex $e 0]]
  546.             regsub -all "\[\n\r\]> " $text "\r" text
  547.             set text [string range $text 1 end]
  548.         } else {
  549.             set p [selEnd]
  550.             set e [search -s -f 1 -r 1 {^---} $p]
  551.             set text [getText $p [lindex $e 0]]
  552.             regsub -all "\[\n\r\]< " $text "\r" text
  553.             set text [string range $text 1 end]
  554.         }
  555.         }
  556.         Diff::BringToFront ${w1}
  557.         gotoMark "diff-$mark"
  558.         nextLine
  559.         insertText -w ${w1} $text
  560.     }
  561.     }
  562.     Diff::diffWinFront
  563. }
  564. proc Diff::patchOld {ww1 ww2 left} {
  565.     upvar \#0 $ww1 w1
  566.     upvar \#0 $ww2 w2
  567.     set code [Diff::line [getPos]]
  568.     if {[scan [Diff::parseDiffLine $code] "%s %d %d %d %d" \
  569.       char start1 end1 start2 end2] != 5} { return }
  570.     
  571.     switch $char${left} {
  572.     "c1" {
  573.         set text [getText -w ${w2} $start2 $end2]
  574.         bringToFront ${w1}
  575.         replaceText $start1 $end1 $text
  576.     }
  577.     "d1" {
  578.         bringToFront ${w1}
  579.         deleteText $start1 $end1
  580.     }
  581.     "a1" {
  582.         set text [getText -w ${w2} $start2 $end2]
  583.         set p [nextLineStart $start1]
  584.         # for some reason this single line won't work instead of the
  585.         # next two!
  586.         #select -w ${Diff::1} $p $p
  587.         bringToFront ${w1}
  588.         goto $p
  589.         insertText -w ${w1} $text
  590.     }
  591.     "c0" {
  592.         set text [getText -w ${w2} $start1 $end1]
  593.         bringToFront ${w1}
  594.         replaceText $start2 $end2 $text
  595.     }
  596.     "d0" {
  597.         set text [getText -w ${w2} $start1 $end1]
  598.         bringToFront ${w1}
  599.         goto $start2
  600.         nextLine
  601.         insertText $text
  602.     }
  603.     "a0" {
  604.         bringToFront ${w1}
  605.         deleteText $start2 $end2
  606.     }
  607.     }
  608.     message "Subsequent insertions will be screwed up"
  609. }
  610.  
  611. # In the diff-window, 'c' = cut from left, replace with given lines,
  612. # 'd' = delete from left, 'a' = add to left.
  613. proc Diff::patchIntoLeftWindow {} {
  614.     Diff::patch Diff::1 Diff::2 1
  615. }
  616.  
  617. proc Diff::patchIntoRightWindow {} {
  618.     Diff::patch Diff::2 Diff::1 0
  619. }
  620.  
  621. # ◊◊◊◊ Main comparison routines ◊◊◊◊ #
  622.  
  623. proc Diff::files {} {
  624.     global Diff::1 Diff::2
  625.     foreach f [list ${Diff::1} ${Diff::2}] {
  626.     if {[lsearch [winNames -f] $f] >= 0} {
  627.         getWinInfo -w $f arr
  628.         if {$arr(dirty)} {
  629.         bringToFront $f
  630.         if {![dialog::yesno "Save this window?"]} { error "Cancel"}
  631.         save
  632.         }
  633.     }
  634.     }
  635.     # make sure newer file is on the right
  636.     if {[file::secondIsOlder ${Diff::1} ${Diff::2}]} {
  637.     set d ${Diff::2}
  638.     set Diff::2 ${Diff::1}
  639.     set Diff::1 $d
  640.     unset d
  641.     }
  642.     Diff::run
  643. }
  644.  
  645. proc Diff::run {} {
  646.     global Diff::handler Diff::handlers
  647.     # call the registered procedure
  648.     [set Diff::handlers([set Diff::handler])]
  649. }
  650.  
  651. proc Diff::runInsideAlpha {} {
  652.     global Diff::1 Diff::2
  653.     Diff::Display Diff::1 1 0 1
  654.     Diff::Display Diff::2 0 0 1
  655.  
  656.     Diff::execute
  657. }
  658.  
  659. proc compare::directories {} {
  660.     global Diff::1 Diff::2
  661.     
  662.     set Diff::1 [get_directory -p "Select 'old' dir 1:"]
  663.     set Diff::2 [get_directory -p "Select 'new' dir 2:"]
  664.     
  665.     Diff::execute 1 {* Directory Comparison *}
  666. }
  667.  
  668. proc compare::files {} {
  669.     global Diff::1 Diff::2
  670.     
  671.     set Diff::1 [getfile "Select your 'old' file:"]
  672.     set Diff::2 [getfile "Select your 'new' file:"]
  673.     
  674.     Diff::files
  675. }
  676.  
  677. proc compare::windows {} {
  678.     global tileHeight tileWidth tileTop tileLeft
  679.     global Diff::1 Diff::2
  680.     
  681.     set wins [winNames -f]
  682.     if {[llength $wins] < 2} { message "Need 2 windows"; return }
  683.     
  684.     set Diff::1 [lindex $wins 0]
  685.     set Diff::2 [lindex $wins 1]
  686.     Diff::files
  687. }
  688.  
  689.  
  690. ## 
  691.  # -------------------------------------------------------------------------
  692.  # 
  693.  # "Diff::execute" --
  694.  # 
  695.  #  Modification of the original to optionally return the diff 
  696.  #  result, rather than opening it in a window
  697.  # 
  698.  # Results:
  699.  # 
  700.  #  Returns 1 if the files are the same and 0 if they differ
  701.  #  
  702.  #  If storeResult is true, the result of the diff operation is stored 
  703.  #  in the global Diff::result, rather than being opened in a window
  704.  # 
  705.  # --Version--Author------------------Changes-------------------------------
  706.  #    1.0     <keleher@cs.umd.edu> original
  707.  #    1.1     <j-guyer@nwu.edu> optionally return diff result in a global
  708.  #    1.2     <j-guyer@nwu.edu> flags set if files were open before compare
  709.  # -------------------------------------------------------------------------
  710.  ##
  711. proc Diff::execute {{isdir 0} {name {* File Comparison *}} {storeResult 0}} {
  712.     global DiffmodeVars \
  713.       Diff::1 Diff::2 win::Modes HOME diffDir Diff::result \
  714.       Diff::1Open Diff::2Open \
  715.       Diff::leftDir Diff::rightDir DiffSig
  716.     
  717.     set Diff::leftDir ""
  718.     set Diff::rightDir ""
  719.     set diffDir $isdir
  720.     
  721.     message "Launching 'GNU Diff'"
  722.     set flags $DiffmodeVars(diffFlags)
  723.     if {$DiffmodeVars(linesOfContext) != 0} {
  724.     append flags " -C $DiffmodeVars(linesOfContext)"
  725.     }
  726.     message "Starting diff…"
  727.     append flags " \"[stripNameCount ${Diff::1}]\" \"[stripNameCount ${Diff::2}]\""
  728.     set dtext [app::runScript Diff "Diff application" "" 1 0 $flags]
  729.     message "Starting diff…done"
  730.     
  731.     if {[lsearch [winNames -f] ${Diff::1}] >= 0} {
  732.     set Diff::1Open 1
  733.     } else {
  734.     set Diff::1Open 0
  735.     }
  736.     if {[lsearch [winNames -f] ${Diff::2}] >= 0} {
  737.     set Diff::2Open 1
  738.     } else {
  739.     set Diff::2Open 0
  740.     }
  741.     
  742.     if {![string length $dtext]} {
  743.     if {!$storeResult} {
  744.         alertnote    "No difference:\r${Diff::1}\r${Diff::2}"
  745.     }
  746.     return 0
  747.     } else {    
  748.     # If requested, return the diff result in Diff::result, 
  749.     # rather than opening a diff window
  750.     if {$storeResult} {
  751.         set Diff::result $dtext
  752.     } else {
  753.         Diff::diffWindow $dtext $name            
  754.     }
  755.     return 1
  756.     }
  757. }
  758.  
  759. proc Diff::displayAll {{name "* File Comparison *"}} {
  760.     global Diff::1 Diff::2 Diff::result
  761.     
  762.     Diff::Display Diff::1 1 0 1
  763.     Diff::Display Diff::2 0 0 1
  764.     Diff::diffWindow ${Diff::result} $name    
  765. }
  766.  
  767. proc Diff::diffWindow {diffText {name {* File Comparison *}}} {
  768.     global tileLeft tileTop tileWidth tileHeight    
  769.     
  770.     set top [expr {$tileTop + $tileHeight - 178}]
  771.     set n [new -n $name -g $tileLeft $top [expr {$tileWidth - 6}] 178 \
  772.       -m Diff -info "\r$diffText\r"]
  773.     select [minPos] [nextLineStart [minPos]]
  774.     Diff::opening $n
  775. }
  776.  
  777. # ◊◊◊◊ Moving around ◊◊◊◊ #
  778. proc Diff::Up {} {
  779.     set res [search -s -f 0 -r 1 -- "^\[^- \n\r\]+(\r|\n|\$)" [pos::math [getPos] - 1]]
  780.     set pos [lindex $res 0]
  781.     select $pos [nextLineStart $pos]
  782.     display $pos
  783.     refresh
  784. }
  785.  
  786. proc Diff::Down {} {
  787.     set res [search -s -f 1 -r 1 -- "^\[^- \n\r\]+(\r|\n|\$)" [pos::math [getPos] + 1]]
  788.     set pos [lindex $res 0]
  789.     select $pos [nextLineStart $pos]    
  790.     display $pos
  791.     refresh
  792. }
  793.  
  794. proc Diff::Select {} {
  795.     global Diff::1 Diff::2 diffDir
  796.     
  797.     set text [getText [lineStart [getPos]] [pos::math [nextLineStart [getPos]] - 1]]
  798.     
  799.     if {![regexp {[acd]} $text char]} return
  800.     set res [split $text $char]
  801.     if {![scan [lindex $res 0] "%d" one]} return
  802.     if {![scan [lindex $res 1] "%d" two]} return
  803.     if {$one == 1} {incr one}
  804.     if {$two == 1} {incr two}
  805.     
  806.     if {$diffDir} {
  807.     set res [search -s -f 0 -r 1 "^diff.*(\r|\n|\$)" [getPos]]
  808.     set text [eval getText $res]
  809.     set len [llength $text]
  810.     set Diff::1 [lindex $text [expr {$len - 2}]]
  811.     set Diff::2 [lindex $text [expr {$len - 1}]]
  812.     }
  813.     Diff::Display Diff::1 1 [expr {$one - 1}] $diffDir
  814.     Diff::Display Diff::2 0 [expr {$two - 1}] $diffDir
  815.     
  816.     if {$diffDir} {
  817.     catch {bringToFront ${Diff::window}}
  818.     }
  819. }
  820.  
  821. proc Diff::Display {name left {row 0} {check 0}} {
  822.     upvar $name wname
  823.     if {![info exists wname]} {
  824.     if {$left} {
  825.         message "Diff window for left doesn't exist"
  826.     } else {
  827.         message "Diff window for right doesn't exist"
  828.     }
  829.     return
  830.     }
  831.     if {$check} {
  832.     set geo [Diff::Geo $left]
  833.     set res [lsearch [winNames -f] "$wname"];
  834.     if { $res < 0 } {
  835.         set res [lsearch [winNames -f] "$wname <*>"];
  836.     }
  837.     if { $res < 0} {
  838.         eval edit -g $geo [list $wname]
  839.         set wname [win::Current]
  840.     } else {
  841.         set wname [lindex [winNames -f] $res]
  842.         if {[getGeometry $wname] != $geo} {
  843.         sizeWin $wname [lindex $geo 2] [lindex $geo 3]
  844.         moveWin $wname [lindex $geo 0] [lindex $geo 1]
  845.         }
  846.         if {$res > 2} {
  847.         bringToFront $wname
  848.         }
  849.     }
  850.     }
  851.     display -w $wname [rowColToPos -w $wname $row 0]
  852. }
  853.  
  854. proc Diff::viewSophisticated {} {
  855.     global Diff::1 Diff::2 diffDir DiffmodeVars Diff::Marked
  856.     global Diff::leftDir Diff::rightDir
  857.     
  858.     set text [Diff::line [getPos] files]
  859.     
  860.     if {$diffDir} {
  861.     set Diff::1 ${Diff::leftDir}[lindex $files 0]
  862.     if {![file exists ${Diff::1}]} {
  863.         if {${Diff::leftDir} == "" && ([set res [lsearch [winNames] "[file tail ${Diff::1}]*"]] != -1)} {
  864.         set Diff::1 [lindex [winNames -f] $res]
  865.         } else {
  866.         unset Diff::1
  867.         }
  868.     } else {
  869.         if {[set res [lsearch [winNames -f] "${Diff::1}*"]] != -1} {
  870.         set Diff::1 [lindex [winNames -f] $res]
  871.         }
  872.     }
  873.     set Diff::2 ${Diff::rightDir}[lindex $files 1]
  874.     if {![file exists ${Diff::2}]} {
  875.         if {${Diff::rightDir} == "" && ([set res [lsearch [winNames] "[file tail ${Diff::2}]*"]] != -1)} {
  876.         set Diff::2 [lindex [winNames -f] $res]
  877.         } else {
  878.         unset Diff::2
  879.         }
  880.     } else {
  881.         if {[set res [lsearch [winNames -f] "${Diff::2}*"]] != -1} {
  882.         set Diff::2 [lindex [winNames -f] $res]
  883.         }
  884.     }
  885.     }
  886.     regexp {([^/]+)(.*)} $text "" mark suff
  887.     if {![info exists "Diff::Marked($suff)"]} {
  888.     Diff::Display Diff::1 1 0 1
  889.     Diff::Display Diff::2 0 0 1
  890.     Diff::setMarksUp $suff
  891.     if {[info exists Diff::1]} {
  892.         Diff::mark ${Diff::1} 1 $suff
  893.         set Diff::Marked($suff) 1
  894.     }
  895.     if {[info exists Diff::2]} {
  896.         Diff::mark ${Diff::2} 0 $suff
  897.         set Diff::Marked($suff) 1
  898.     }
  899.     Diff::diffWinFront
  900.     }
  901.     set text $mark
  902.     
  903.     if {$DiffmodeVars(useMarksDontBringToFront)} {
  904.     if {![catch {mark::getRange diff-$text ${Diff::1}} range]} {
  905.         set beg [lindex $range 0]
  906.         set end [lindex $range 2]
  907.         display -w ${Diff::1} [expr \
  908.           {[pos::compare $beg > [minPos]] \
  909.           ? [pos::math $beg - 1] : $beg}]
  910.         select -w ${Diff::1} $beg $end
  911.         if {[pos::compare $beg > [minPos]]} {
  912.         set beg [pos::math $beg - 1]
  913.         }
  914.         #display -w ${Diff::1} $beg
  915.         #refresh ${Diff::1}
  916.     }
  917.     
  918.     if {![catch {mark::getRange diff-$text ${Diff::2}} range]} {
  919.         set beg [lindex $range 0]
  920.         set end [lindex $range 2]
  921.         display -w ${Diff::2} [expr \
  922.           {[pos::compare $beg > [minPos]] \
  923.           ? [pos::math $beg - 1] : $beg}]
  924.         select -w ${Diff::2} $beg $end
  925.         if {[pos::compare $beg > [minPos]]} {
  926.         set beg [pos::math $beg - 1]
  927.         }
  928.         #display -w ${Diff::2} $beg
  929.         #refresh ${Diff::2}
  930.     }
  931.     # we need this line because of an Alpha visual bug.
  932.     # Alpha will often draw the text in the wrong window when we 
  933.     # hit 'down'.  It does correct itself, but it looks silly.
  934.     Diff::diffWinFront
  935.     } else {            
  936.     if {![catch {Diff::BringToFront ${Diff::1}}]} {
  937.         gotoMark "diff-$text"
  938.     }
  939.     if {![catch {Diff::BringToFront ${Diff::2}}]} {
  940.         gotoMark "diff-$text"        
  941.     }
  942.     Diff::diffWinFront
  943.     }
  944. }
  945. proc Diff::viewOld {} {
  946.     global Diff::1 Diff::2 diffDir 
  947.     
  948.     set text [Diff::line [getPos]]
  949.     if {![regexp {[acd]} $text char]} return
  950.     set res [split $text $char]
  951.     if {![scan [lindex $res 0] "%d,%d" one oned]} return
  952.     if {![scan [lindex $res 1] "%d,%d" two twod]} return
  953.     set on $one
  954.     set tw $two
  955.     if {$on == 1} {incr on}
  956.     if {$tw == 1} {incr tw}
  957.     if {![info exists oned]} {set oned $one}
  958.     if {![info exists twod]} {set twod $two}
  959.     
  960.     if {$diffDir} {
  961.     set res [search -s -f 0 -r 1 "^diff.*(\r|\n|\$)" [getPos]]
  962.     set text [eval getText $res]
  963.     set Diff::1 [lindex $text 1]
  964.     set Diff::2 [lindex $text 2]
  965.     }
  966.     Diff::Sel Diff::1 [expr {$on - 1}] $one $oned 1
  967.     Diff::Sel Diff::2 [expr {$tw - 1}] $two $twod 0
  968.     set wins [lremove [lrange [winNames -f] 0 2] ${Diff::1} ${Diff::2}]
  969.     set wins [lremove -glob $wins *Comparison*]
  970.     if {$wins != ""} {
  971.     bringToFront ${Diff::1}
  972.     bringToFront ${Diff::2}
  973.     }
  974.     Diff::diffWinFront
  975. }
  976.  
  977. proc Diff::View {} {
  978.     global DiffmodeVars
  979.     if {$DiffmodeVars(useSophisticatedDiffMarking)} {
  980.     Diff::viewSophisticated
  981.     } else {
  982.     Diff::viewOld
  983.     }
  984. }
  985.  
  986. ## 
  987.  # -------------------------------------------------------------------------
  988.  # 
  989.  # "Diff::Sel" --
  990.  # 
  991.  #  This handles a name either with or without trailing '<n>' and fixes
  992.  #  the given name if it isn't right.
  993.  # -------------------------------------------------------------------------
  994.  ##
  995. proc Diff::Sel {wnamev ro row rowd left} {
  996.     global diffDir
  997.     upvar $wnamev wname
  998.     if {$diffDir} {
  999.     set geo [Diff::Geo $left]
  1000.     if {[set res [lsearch [winNames -f] "$wname*"]] < 0} {
  1001.         eval edit -g $geo [list $wname]
  1002.         set wname [win::Current]
  1003.     } else {
  1004.         set wname [lindex [winNames -f] $res]
  1005.         if {[getGeometry $wname] != $geo} {
  1006.         sizeWin $wname [lindex $geo 2] [lindex $geo 3]
  1007.         moveWin $wname [lindex $geo 0] [lindex $geo 1]
  1008.         }
  1009.     }
  1010.     }
  1011.     display -w $wname [rowColToPos -w $wname $ro 0]
  1012.     select -w $wname [rowColToPos -w $wname $row 0] \
  1013.       [rowColToPos -w $wname [expr {$rowd + 1}] 0]
  1014. }
  1015.  
  1016. # ◊◊◊◊ Utilities ◊◊◊◊ #
  1017.  
  1018. proc Diff::Win {} {
  1019.     global win::Modes
  1020.     set files [winNames -f]
  1021.     set len [llength $files]
  1022.     for {set i 0} {$i < $len} {incr i} {
  1023.     if {[set win::Modes([lindex [winNames -f] $i])] == "Diff"} {
  1024.         bringToFront [lindex [winNames] $i]
  1025.         return
  1026.     }
  1027.     }
  1028.     beep
  1029.     message "No Diff window."
  1030. }
  1031.  
  1032. proc Diff::Geo {left} {
  1033.     global tileWidth tileHeight tileTop tileLeft
  1034.     
  1035.     set margin 4
  1036.     set width [expr {($tileWidth - $margin)/2}]
  1037.     set height [expr {$tileHeight - 200}]
  1038.     set hor $tileLeft
  1039.     
  1040.     if {!$left} {incr hor [expr {$width+$margin}]}
  1041.     
  1042.     return [list $hor $tileTop $width $height]
  1043. }
  1044.  
  1045. proc Diff::diffWinFront {} {
  1046.     global Diff::window
  1047.     catch {bringToFront ${Diff::window}}
  1048. }
  1049.  
  1050. ## 
  1051.  # -------------------------------------------------------------------------
  1052.  # 
  1053.  # "Diff::BringToFront" --
  1054.  # 
  1055.  #  Hack to make it quicker to switch between windows.  We often want
  1056.  #  the 'Diff' window to be in the front all the time, but have to
  1057.  #  bring others to the front temporarily for manipulation.  This proc
  1058.  #  brings a different window to the front more quickly by avoiding
  1059.  #  all mode-changing code.  Of course you should only call this proc
  1060.  #  when you will _very_ soon bring a different window to the front.
  1061.  # -------------------------------------------------------------------------
  1062.  ##
  1063. proc Diff::BringToFront {w} {
  1064.     global win::Modes DiffmodeVars
  1065.     if {$DiffmodeVars(useFastWindowSwapping)} {
  1066.     set oldm [set win::Modes($w)]
  1067.     set win::Modes($w) Diff
  1068.     if {[catch {bringToFront $w}]} {
  1069.         unset win::Modes($w)
  1070.         beep
  1071.         error "no such win"
  1072.     } else {
  1073.         set win::Modes($w) $oldm
  1074.     }
  1075.     } else {
  1076.     bringToFront $w
  1077.     }
  1078. }
  1079.